﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Net;

namespace PLock {
	class Entry {
		public Entry() {
			_guid = Guid.NewGuid();
		}
		public Entry(Guid guid) {
			_guid = guid;
		}

		private string _writekey = null;
		private byte[] _writesalt = null;
		private Guid _guid;

		public void SetWriteKey(string writekey, byte[] salt) {
			_writekey = writekey;
			_writesalt = salt;
		}

		public Guid Guid { get { return _guid; } }

		private string FilePath(string ext) {
			return Settings.Instance.FullPath(Filename(ext));
		}
		private string Filename(string ext) {
			return string.Format("{0}.{1}", _guid.ToString("D"), ext);
		}

		public void Delete() {
			foreach (string ext in new string[] { "label", "user", "pass", "snip" }) {
				if (File.Exists(FilePath(ext)))
					File.Delete(FilePath(ext));
			}
		}

		public string ReadLabel() {
			if (!File.Exists(FilePath("label")))
				return "";
			return File.ReadAllText(FilePath("label"), Encoding.UTF8);
		}
		public string ReadUsername() {
			return ReadCrypt("user");
		}
		public bool HasPassword {
			get {
				return File.Exists(FilePath("pass"));
			}
		}
		public string ReadPassword() {
			return ReadCrypt("pass");
		}
		public string ReadSnippet() {
			return ReadCrypt("snip");
		}

		public void WriteLabel(string value) {
			using (StreamWriter sw = new StreamWriter(FilePath("label"), false, new UTF8Encoding(false)))
				sw.Write(value);
		}
		public void WriteUsername(string value) {
			WriteCrypt("user", value);
		}
		public void WritePassword(string value) {
			WriteCrypt("pass", value);
		}
		public void WriteSnippet(string value) {
			WriteCrypt("snip", value);
		}

		public void WriteCrypt(string ext, string value) {
			if (value == "")
				File.Delete(FilePath(ext));

			HashAlgorithm hash = PasswordDlg.HashAlgorithm();
			SymmetricAlgorithm sym = PasswordDlg.SymmetricAlgorithm();
			RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

			byte[] clear = new UTF8Encoding(false).GetBytes(value);
			byte[] digest = hash.ComputeHash(clear);
			byte[] len = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(clear.Length));
			byte[] iv = new byte[sym.BlockSize / 8];
			rng.GetBytes(iv);

			using (FileStream fs = File.Create(FilePath(ext))) {
				fs.Write(new byte[] { (byte)'P', (byte)'L', (byte)'K', 0x01 }, 0, 4);
				fs.Write(iv, 0, iv.Length);

				using (SelfDestruct key = PasswordDlg.GetKey(Filename(ext), _writekey, _writesalt)) {
					using (CryptoStream stream = new CryptoStream(fs, sym.CreateEncryptor(key.Bytes, iv), CryptoStreamMode.Write)) {
						stream.Write(len, 0, len.Length);
						stream.Write(digest, 0, digest.Length);
						stream.Write(clear, 0, clear.Length);
					}
				}
			}
		}

		public string ReadCrypt(string ext) {
			HashAlgorithm hash = PasswordDlg.HashAlgorithm();
			SymmetricAlgorithm sym = PasswordDlg.SymmetricAlgorithm();

			if (!File.Exists(FilePath(ext)))
				return "";
			using (FileStream fs = File.OpenRead(FilePath(ext))) {
				byte[] sig = new byte[4];
				if (fs.Read(sig, 0, sig.Length) < sig.Length ||
					sig[0] != 'P' || sig[1] != 'L' || sig[2] != 'K' || sig[3] != 0x01)
					throw new Exception("Not a P-Lock encrypted file");
				byte[] iv = new byte[sym.BlockSize / 8];
				if (fs.Read(iv, 0, iv.Length) < iv.Length)
					throw new Exception("File has been truncated");

				MemoryStream clearstream = new MemoryStream();
				using (SelfDestruct key = PasswordDlg.GetKey(Filename(ext))) {
					using (CryptoStream stream = new CryptoStream(fs, sym.CreateDecryptor(key.Bytes, iv), CryptoStreamMode.Read)) {
						byte[] buf = new byte[4096];
						while (true) {
							int cnt = stream.Read(buf, 0, buf.Length);
							if (cnt == 0)
								break;
							clearstream.Write(buf, 0, cnt);
						}
					}
				}

				clearstream.Seek(0, SeekOrigin.Begin);
				byte[] len = new byte[4];
				if (clearstream.Read(len, 0, len.Length) < len.Length)
					throw new Exception("Invalid encrypted data");
				int clearlen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(len, 0));
				byte[] digest = new byte[hash.HashSize / 8];
				if (clearstream.Read(digest, 0, digest.Length) < digest.Length)
					throw new Exception("Invalid encrypted data");
				if (clearlen > (clearstream.Length - len.Length - digest.Length) || clearlen < (clearstream.Length - len.Length - digest.Length - sym.BlockSize))
					throw new Exception("Invalid cleartext length: " + clearlen);
				byte[] clearbytes = new byte[clearlen];
				if (clearstream.Read(clearbytes, 0, clearbytes.Length) < clearbytes.Length)
					throw new Exception("Invalid encrypted data");

				byte[] computeddigest = hash.ComputeHash(clearbytes, 0, clearbytes.Length);
				for (int i = 0; i < digest.Length; i++) {
					if (digest[i] != computeddigest[i])
						throw new Exception("Invalid data: either the file was encrypted with a different password, or it has been modified after encryption");
				}

				return Encoding.UTF8.GetString(clearbytes, 0, clearbytes.Length);
			}
		}
	}
}
